#!/bin/sh
#
# Cryptohome service is a system daemon responsible for remote attestation
# and interacting with the device's TPM Chip.
#
# This script starts the following daemons:
#   - TrouSerS daemon: TPM interaction.
#   - CHAPS daemon: opencryptoki alternative.
#   - Cryptohome Daemon: Remote Attestation + TPM Ussage.
#
# Cryptohome daemon depends on TrouSerS and CHAPS daemons.
# This script will also run the tpm-manager program before starting Cryptohome
# daemon, this is because tpm-manager is used to take ownership of the tpm
# and create the attestation enrollment credentials for the first time.
#
# Note: Cryptohome is ported from ChromeOS as a binary so it expects the ChromeOS
# environment. Therefore, chroot is needed to execute cryptohome.
#

# Sanity check
[ -x /chroot/chromeos/bin/cryptohomed ] || exit 1
[ -x /chroot/chromeos/bin/tcsd ] || exit 1
[ -x /chroot/chromeos/bin/chapsd ] || exit 1
[ -x /chroot/chromeos/bin/tpm-manager ] || exit 1

. /etc/utils.sh

CONFIG_PATH=/var/config
SYNC_MOUNT_FLAGS="-o sync"

# kill_if_running is used to kill an application only if it is running.
# using a pkill alone would return an error if the program couldn't be found.
# The first parameter is the binary name of the program to kill.
#
# example: kill_if_running "cryptohomed"
kill_if_running() {
  echo "Stopping $1..."

  if pgrep -x $1 > /dev/null; then
    pkill -x $1
    [ $? -ne 0 ] && echo "Failed to Stop $1..." && exit 1
  fi
}


# Bind chroot directories to tmpfs, as chroot may exist in a read-only
# filesystem. These directories are used for the DBus socket and Cryptohome
# libraries.

# We need the preserve folder, but also tpm uses /mnt/stateful_partition/.tpm_owned
mkdir -p "$CONFIG_PATH/cryptohome/stateful_partition/unencrypted/preserve"
mount_once "$CONFIG_PATH/cryptohome/stateful_partition" "/chroot/chromeos/mnt/stateful_partition"

# Home folder, for storing /home/.shadow/cryptohome.key
mkdir -p "$CONFIG_PATH/cryptohome/home"
mount_once "$CONFIG_PATH/cryptohome/home" "/chroot/chromeos/home"

# Root home folder, for storing trousers information
mkdir -p "$CONFIG_PATH/cryptohome/root"
mount_once "$CONFIG_PATH/cryptohome/root" "/chroot/chromeos/root"

# /var/lib stores the old .tpm_owned and opencryptoki files. Also /var/lib/tpm folder is required for trousers.
mkdir -p "$CONFIG_PATH/shared/lib/tpm"
mkdir -p "$CONFIG_PATH/shared/lib/metrics"
mkdir -p "$CONFIG_PATH/shared/lib/buffet" # for mount point
[ -e "$CONFIG_PATH/cryptohome/lib" ] || ln -sf "$CONFIG_PATH/shared/lib" "$CONFIG_PATH/cryptohome/lib"
mount_once "$CONFIG_PATH/shared/lib" "/chroot/chromeos/var/lib"

# Delete metrics data. It is not used and can leave /var/config without disk space.
rm -f "$CONFIG_PATH/shared/lib/metrics/uma-events"

# /etc is needed because we need to access users and groups and /etc/tcsd.conf
# trousers needs to check that it is running with user and group tss:tss
mount_once "/etc" "/chroot/chromeos/etc"

# /dev is needed for access to /dev/tpm0, /dev/urandom, /dev/log and /dev/null
mount_once "/dev" "/chroot/chromeos/dev"

# /sys is required to easily interact with the tpm via /sys/class/misc/tpm0
mount_once "/sys" "/chroot/chromeos/sys"

mount_once "/tmp/run" "/chroot/chromeos/var/run"
mount_once "/tmp" "/chroot/chromeos/tmp"

# Sanity check.. DBus socket has to be present.
wait-until-created /chroot/chromeos/var/run/dbus/system_bus_socket

create_new_config() {
  FLAGS="$1"
  IMG_PATH="$2"

  if ! rm -f ${IMG_PATH} || ! rm -f ${IMG_PATH}.key ; then
    echo "Failed to remove old config partition files!"
    return 1
  fi

  CONFIG_PART=$(find_sata_blkdev)20

  ORIG_MOUNT_OPTS=$(grep ${CONFIG_PATH} /proc/mounts | sed -e "s/\S\+ \S\+ \S\+ \(.\+\) .\+ /\1/")

  if [ "${FLAGS}" = "${SYNC_MOUNT_FLAGS}" ]; then
    # Remount /var/config asynchronously to speed up formatting
    ASYNC_MOUNT_OPTS=$(echo ${ORIG_MOUNT_OPTS} | sed -e "s/,sync,/,/")
    mount -o remount,${ASYNC_MOUNT_OPTS} ${CONFIG_PART} ${CONFIG_PATH}
  fi

  if ! dd if=/dev/zero of=$IMG_PATH bs=1024 count=40960 ; then
    echo "Failed to create empty config partition file!"
    return 1
  fi
}

format_new_config() {
  FLAGS="$1"
  IMG_DEVICE="$2"

  echo "Formatting encrypted config partition..."
  mkfs.ext4 -q $IMG_DEVICE
  MKFS_STATUS="$?"

  if [ "${FLAGS}" = "${SYNC_MOUNT_FLAGS}" ]; then
    # Re-establish synchronous mount
    mount -o remount,${ORIG_MOUNT_OPTS} ${CONFIG_PART} ${CONFIG_PATH}
  fi

  if [ "${MKFS_STATUS}" != "0" ]; then
    echo "Failed to format config partition!"
    return 1
  fi
}

mount_config() {
  IMG_PATH=$CONFIG_PATH/config.img
  IMG_DEVICE=/dev/mapper/config

  echo "Mounting encrypted config partition..."

  # Stop if /config is already mounted.
  if mount | grep -q "on /config" ; then
    return 0
  fi

  FLAGS=""
  mount | grep ${CONFIG_PATH} | grep ,sync
  if [ $? = "0" ]; then
    FLAGS="${SYNC_MOUNT_FLAGS}"
  fi

  # Create new config only if TPM was previously un-owned.
  if [ -f "${CONFIG_PATH}/config.init" ] ; then
    create_new_config "${FLAGS}" "${IMG_PATH}" || exit 1
  fi

  # cryptdev will wait for cryptohome service.
  if ! cryptdev config $IMG_PATH ; then
    echo "Cryptdev command failed!"
    exit 1
  fi

  wait-until-created $IMG_DEVICE

  # Format new config partition.
  if [ -f "${CONFIG_PATH}/config.init" ] ; then
    format_new_config "${FLAGS}" "${IMG_DEVICE}" || exit 1
    rm -f "${CONFIG_PATH}/config.init" || exit 1
  fi

  if ! mount -t ext4 $FLAGS $IMG_DEVICE /config ; then
    echo "Failed to mount encrypted config partition!"
    exit 1
  fi

  # Make /config writable to non-root.
  chown bin.sys /config
  chmod 775 /config

  return 0
}

start() {
  echo "Starting cryptohome services..."

  PCR0=$(chroot /chroot/chromeos /bin/tpmc pcrread 0)
  PCR0_INIT_PATH=${CONFIG_PATH}/pcr0.init

  if [ -f ${PCR0_INIT_PATH} ]; then
    PCR0_INIT=$(cat ${PCR0_INIT_PATH})

    if [ x"${PCR0}" != x"${PCR0_INIT}" ]; then
      echo "Bootmode has changed: wiping data then reboot..."
      /bin/zap --i-really-mean-it --erase-backups
      reboot
    fi
  else
    echo -n "${PCR0}" > ${PCR0_INIT_PATH}
  fi

  echo "starting TCSD..."
  chroot /chroot/chromeos /bin/tcsd 2>&1 | logos tcsd

  echo "starting CHAPSD..."
  babysit 10 chroot /chroot/chromeos /bin/chapsd 2>&1 | logos chapsd &

  chroot /chroot/chromeos /bin/tpm-manager dump_status 2>&1 | grep -q "owned: true"
  if [ "$?" != "0" ] ; then
    touch "${CONFIG_PATH}/config.init" || exit 1
  fi

  chroot /chroot/chromeos /bin/tpm-manager 2>&1
  [ $? -ne 0 ] && echo "Failed to run tpm-manager..." && stop && exit 1
  echo "tpm-manager ran successfully"

  echo "starting Cryptohomed..."
  # babysit and logos do not work because cryptohomed does daemonize.
  chroot /chroot/chromeos /bin/cryptohomed 2>&1

  # Encrypted partition depends on cryptohome for encryption key.
  mount_config 2>&1 | logos configfs
}

stop() {
  echo "Stopping cryptohome services"
  kill_if_running "cryptohomed"
  kill_if_running "chapsd"
  kill_if_running "tcsd"
  echo "Cryptohome Services stopped successfully"
}

restart() {
  stop
  start
}

case "$1" in
  start)
    start
    ;;
  stop)
    stop
    ;;
  restart|reload)
    restart
    ;;
  *)
    echo "Usage: $0 {start|stop|restart}"
    exit 1
esac

